/*-
 * Copyright (c) 2008-2010, Oleg Estekhin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the distribution.
 *  * Neither the names of the copyright holders nor the names of their
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

package oe.assertions;

import java.util.Formatter;

/** A buffer that contains assertion details. */
public class MessageBuffer implements Appendable {

    private final MessageBuffer parent;

    private final int indentationLevel;

    private final StringBuilder buffer;

    private Formatter formatter;


    /** Creates a new message buffer. */
    public MessageBuffer() {
        parent = null;
        indentationLevel = 0;
        buffer = new StringBuilder();
    }

    /**
     * Creates a new message buffer with the specified parent buffer and indentation level.
     *
     * @param parent the parent buffer
     * @param indentationLevel the indentation level
     *
     * @throws IllegalArgumentException if {@code parent} is {@code null}
     * @throws IllegalArgumentException if {@code indentationLevel} is negative
     */
    public MessageBuffer( MessageBuffer parent, int indentationLevel ) {
        if ( parent == null ) {
            throw new IllegalArgumentException( "parent is null" );
        }
        if ( indentationLevel < 0 ) {
            throw new IllegalArgumentException( "indentationLevel is negative" );
        }
        this.parent = parent;
        this.indentationLevel = indentationLevel;
        buffer = new StringBuilder();
    }


    /** Publishes the contents of this buffer. */
    public void publish() {
        if ( formatter != null ) {
            formatter.flush();
        }
        if ( parent != null ) {
            parent.append( buffer );
        }
    }

    /** Disposes this buffer. */
    public void dispose() {
        if ( formatter != null ) {
            formatter.close();
        }
    }


    /**
     * Returns the indentation level configured for this buffer.
     *
     * @return the indentation level
     */
    public int getIndentationLevel() {
        return indentationLevel;
    }

    /**
     * Appends an indentation to this buffer.
     *
     * @return this buffer
     */
    public MessageBuffer indent() {
        for ( int i = indentationLevel; i > 0; i-- ) {
            append( "    " );
        }
        return this;
    }


    /**
     * Appends a formatted string to this buffer using the specified format string and arguments.
     *
     * @param format a format string
     * @param arguments a format arguments
     *
     * @return this buffer
     *
     * @see Formatter#format(String, Object...)
     */
    public MessageBuffer format( String format, Object... arguments ) {
        if ( formatter == null ) {
            formatter = new Formatter( buffer, null );
        }
        formatter.format( null, format, arguments );
        return this;
    }

    /**
     * Appends a formatted value to this buffer.
     *
     * @param value a value to append
     *
     * @return this buffer
     */
    public MessageBuffer formatValue( Object value ) {
        if ( value == null ) {
            return format( "<null>" );

        } else if ( value instanceof Byte ) {
            Byte number = ( Byte ) value;
            return format( "<%s (0x%<02x)>", number );
        } else if ( value instanceof Short ) {
            Short number = ( Short ) value;
            return format( "<%s (0x%<04x)>", number );
        } else if ( value instanceof Integer ) {
            Integer number = ( Integer ) value;
            return format( "<%s (0x%<08x)>", number );
        } else if ( value instanceof Long ) {
            Long number = ( Long ) value;
            return format( "<%sL (0x%<016x)>", number );

        } else if ( value instanceof Float ) {
            Float number = ( Float ) value;
            return format( "<%sf (%<a)>", number );
        } else if ( value instanceof Double ) {
            Double number = ( Double ) value;
            return format( "<%s (%<a)>", number );

        } else if ( value instanceof Character ) {
            Character c = ( Character ) value;
            return format( "<'%s' (u%04x)>", c, ( short ) c.charValue() );
        } else if ( value instanceof String ) {
            // todo escape special symbols, \\, \" and \r\n in particular
            return format( "<\"%s\">", value );

//        todo format individual array elements either the same way or some kind of "short" way as single items above
//        } else if ( value instanceof byte[] ) {
//            byte[] array = ( byte[] ) value;
//            return format( "<%s>", Arrays.toString( array ) );
//        } else if ( value instanceof short[] ) {
//            short[] array = ( short[] ) value;
//            return format( "<%s>", Arrays.toString( array ) );
//        } else if ( value instanceof int[] ) {
//            int[] array = ( int[] ) value;
//            return format( "<%s>", Arrays.toString( array ) );
//        } else if ( value instanceof long[] ) {
//            long[] array = ( long[] ) value;
//            return format( "<%s>", Arrays.toString( array ) );
//
//        } else if ( value instanceof float[] ) {
//            float[] array = ( float[] ) value;
//            return format( "<%s>", Arrays.toString( array ) );
//        } else if ( value instanceof double[] ) {
//            double[] array = ( double[] ) value;
//            return format( "<%s>", Arrays.toString( array ) );
//
//        } else if ( value instanceof char[] ) {
//            char[] array = ( char[] ) value;
//            return format( "<%s>", Arrays.toString( array ) );
//        } else if ( value instanceof boolean[] ) {
//            boolean[] array = ( boolean[] ) value;
//            return format( "<%s>", Arrays.toString( array ) );
//
//        } else if ( value instanceof Object[] ) {
//            Object[] array = ( Object[] ) value;
//            return format( "<%s>", Arrays.deepToString( array ) );

        } else {
            return format( "<%s>", value );
        }
    }


    /**
     * Appends the system-dependent line separator to this buffer.
     *
     * @return this buffer
     */
    public MessageBuffer eol() {
        return format( "%n" );
    }


    @Override
    public MessageBuffer append( CharSequence charSequence ) {
        buffer.append( charSequence );
        return this;
    }

    @Override
    public MessageBuffer append( CharSequence charSequence, int start, int end ) {
        buffer.append( charSequence, start, end );
        return this;
    }

    @Override
    public MessageBuffer append( char c ) {
        buffer.append( c );
        return this;
    }


    @Override
    public String toString() {
        return buffer.toString();
    }

}
